﻿package away3d.entities ;


import away3d.animators.AnimatorBase ;
import away3d.animators.data.AnimationBase ;
import away3d.animators.data.AnimationStateBase ;
import away3d.arcane ;
import away3d.core.base.Geometry ;
import away3d.core.base.IMaterialOwner ;
import away3d.core.base.Object3D ;
import away3d.core.base.SubGeometry ;
import away3d.core.base.SubMesh ;
import away3d.core.partition.EntityNode ;
import away3d.core.partition.MeshNode ;
import away3d.events.GeometryEvent ;
import away3d.library.assets.AssetType ;
import away3d.library.assets.IAsset ;
import away3d.materials.MaterialBase ;



/**
 * Mesh is an instance of a Geometry, augmenting it with a presence in the scene graph, a material, and an animation
 * state. It consists out of SubMeshes, which in turn correspond to SubGeometries. SubMeshes allow different parts
 * of the geometry to be assigned different materials.
 */
class Mesh extends Entity implements IMaterialOwner, IAsset
{
    
    private var _subMeshes      : Vector<SubMesh> ;
    private  var _geometry      : Geometry ;
    private var _material       : MaterialBase ;
    public var _animationState  : AnimationStateBase ;
    private var _mouseDetails   : Bool ;
    private var _castsShadows   : Bool ;

    /**
     * Create a new Mesh object.
     * @param material The material with which to render the Mesh.
     * @param geometry The geometry used by the mesh that provides it with its shape.
     */
    public function Mesh( ?material : MaterialBase = null, ?geometry : Geometry = null )
    {
        
        _castsShadows = true ;
        super() ;
        _geometry = geometry || new Geometry() ;
        _geometry.addEventListener( GeometryEvent.BOUNDS_INVALID,       onGeometryBoundsInvalid ) ;
        _geometry.addEventListener( GeometryEvent.SUB_GEOMETRY_ADDED,   onSubGeometryAdded      ) ;
        _geometry.addEventListener( GeometryEvent.SUB_GEOMETRY_REMOVED, onSubGeometryRemoved    ) ;
        _geometry.addEventListener( GeometryEvent.ANIMATION_CHANGED,    onAnimationChanged      ) ;
        _subMeshes  = new Vector() ;
        material    = material ;
        if( geometry ) initGeometry() ;
        
    }
    
    
    public var assetType( get_assetType, null ) : String ;
    
    
    public override function get_assetType() : String
    {
        
        return AssetType.MESH ;
        
    }
    
    
    private function onGeometryBoundsInvalid( event : GeometryEvent )
    {
        
        invalidateBounds() ;
        
    }
    
    
    public var mouseDetail( get_mouseDetail, set_mouseDetail ) : Bool ;
    
    
    /**
     * Indicates whether or not mouse events contain UV and position coordinates. Setting this to true can affect performance. Defaults to false.
     */
    private function get_mouseDetails() : Bool
    {
        
        return _mouseDetails ;
        
    }
    
    
    private function set_mouseDetails( value : Bool )
    {
        
        _mouseDetails = value ;
        
    }
    
    
    public var castsShadows( get_castsShadows, set_castsShadows ) : Bool ;
    
    
    /**
     * Indicates whether or not the Mesh can cast shadows
     */
    private function get_castsShadows() : Bool
    {
        return _castsShadows ;
    }
    
    
    private function set_castsShadows( value : Bool ) : Bool
    {
        _castsShadows = value ;
        return value ;
        
    }
    
    
    public var animationState( get_animationState, set_animationState ) : AnimatinoStateBase ;
    
    
    /**
     * The animation state of the mesh, defining how the animation should influence the mesh's geometry.
     */
    private function get_animationState() : AnimationStateBase
    {
        
        return _animationState ;
        
    }
    
    
    private function set_animationState( value : AnimationStateBase ) : AnimationStateBase
    {
        
        if( _animationState ) _animationState.removeOwner( this ) ;
        _animationState = value ;
        if( _animationState ) _animationState.addOwner( this ) ;
        
        return value ;
        
    }
    
    
    public var geometry( get_geometry, null ) : Geometry
    
    
    /**
     * The geometry used by the mesh that provides it with its shape.
     */
    private function get_geometry() : Geometry
    {
        
        return _geometry ;
        
    }
    
    
    public var material( get_material, set_material ) : MaterialBase ;
    
    
    /**
     * The material with which to render the Mesh.
     */
    private function get_material() : MaterialBase
    {
        
        return _material ;
        
    }
    
    
    private function set_material( value : MaterialBase )
    {
        
        if( value == _material ) return ;
        if( _material ) _material.removeOwner( this ) ;
        _material = value ;
        if( _material ) _material.addOwner( this ) ;
        return value ;
        
    }
    
    
    public var animation( get_animation, null ) : AnimationBase ;
    
    
    /**
     * The type of animation used to influence the geometry.
     */
    private function get_animation() : AnimationBase
    {
        
        return _geometry.animation ;
        
    }
    
    
    public var subMeshes( get_subMeshes, null ) : Vector<SubMesh> ;
    
    
    /**
     * The SubMeshes out of which the Mesh consists. Every SubMesh can be assigned a material to override the Mesh's
     * material.
     */
    private function get_subMeshes() : Vector<SubMesh>
    {
        
        return _subMeshes ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override public function dispose( deep : Bool )
    {
        
        if( deep )
        {
            
            _geometry.dispose() ;
            
            if( _material )
            {
                
                _material.dispose( true ) ;
                material = null ;
                
            }
            
        }
        
    }
    
    
    /**
     * @inheritDoc
     */
    override public function clone() : Object3D
    {
        
        var clone           = new Mesh( _material, geometry ) ;
//            clone._animationState = _animationState? _animationState.clone() : null ;
        clone.transform     = transform ;
        clone.pivotPoint    = pivotPoint  ;
        clone.bounds        = _bounds.clone() ;
        var len             = _subMeshes.length ;
        
        for( i in 0...len )
        {
            
            clone._subMeshes[ i ]._material = _subMeshes[ i ]._material ;
            
        }
        
        // todo: is there more to be cloned?
        return clone ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override public function updateBounds()
    {
        
        _bounds.fromGeometry( _geometry ) ;
        _boundsInvalid = false ;
        
    }
    
    
    /**
     * @inheritDoc
     */
    override private function createEntityPartitionNode() : EntityNode
    {
        
        return new MeshNode( this ) ;
        
    }
    
    
    /**
     * Initialises the SubMesh objects to map unto the Geometry's SubGeometry objects.
     */
    private  function initGeometry()
    {
        
        var subGeoms = _geometry.subGeometries ;
        
        for( i in 0...subGeoms.length )
        {
            
            addSubMesh( subGeoms[ i ] ) ;
            
        }
        if( _geometry.animation ) animationState = _geometry.animation.createAnimationState() ;
        
    }
    
    
    /**
     * Called when a SubGeometry was added to the Geometry.
     */
    private function onSubGeometryAdded( event : GeometryEvent )
    {
        
        addSubMesh( event.subGeometry ) ;
        
    }
    
    
    /**
     * Called when a SubGeometry was removed from the Geometry.
     */
    private function onSubGeometryRemoved( event : GeometryEvent )
    {
        var subMesh : SubMesh ;
        var subGeom = event.subGeometry ;
        var len     = _subMeshes.length ;
        var j       : Int;
        for ( i in 0...len )
        {
            j = i ;
            subMesh = _subMeshes[ i ] ;
            
            if( subMesh.subGeometry == subGeom )
            {
                
                _subMeshes.splice( i, 1 ) ;
                break ;
                
            }
            
        }
        
        // TODO: check this is correct
        --len ;
        for ( i j...len )
        {
            
            _subMeshes[ i ]._index = i ;
            
        }
        
    }
    
    
    /**
     * Adds a SubMesh wrapping a SubGeometry.
     */
    private function addSubMesh( subGeometry : SubGeometry )
    {
        
        var subMesh         = new SubMesh( subGeometry, this, null ) ;
        var len             = _subMeshes.length ;
        subMesh._index      = len ;
        _subMeshes[ len ]   = subMesh ;
        
    }
    
    
    /**
     * Called when the Geometry's animation type was changed.
     */
    private function onAnimationChanged( event : GeometryEvent )
    {
        
        animationState = _geometry.animation.createAnimationState() ;
        
    }
    
    
}
